package com.boilermaker.mq.mqclient;

import com.boilermaker.mq.common.BinaryTool;
import com.boilermaker.mq.common.MqException;
import com.boilermaker.mq.common.Request;
import com.boilermaker.mq.common.Response;
import com.boilermaker.mq.common.response_payload.BasicReturns;
import com.boilermaker.mq.common.response_payload.SubscribeReturns;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 * Connection 持有 Socket 对象，与服务器（中的任一虚拟主机）建立 TCP 连接，进行网络通信。
 * 每个 Connection 中可设多个 Channel（会话），以减少 TCP 的连接开销。
 * Channel 是独立的消费者。每个 Channel 都可以独立定义处理消息的方法（回调函数），且可以影响所连接虚拟主机中的参数设置。
 *
 * 客户端对服务器的请求通过 Channel 完成。服务器的响应与 Connection 直接接触，Connection 将响应分为 ‘消息’ 和 ‘设置结果’ 两种。
 * 若响应为 ‘消息’，则在 Connection 层处理。若响应为 ‘设置结果’，则将结果（boolean）告知下层 Channel。
 */

@Slf4j
@Getter
public class Connection {
    // 每个 Connection 持有独立的 Socket 对象
    private final Socket socket;
    // 记录 Connection 中的多个 Channel。结构：key-channelId val-Channel
    private final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
    // 该线程池用于处理客户端自定义的回调函数
    private final ExecutorService callbackPool;
    // 基于 Socket 进行网络交互
    private final DataInputStream dataInputStream;
    private final DataOutputStream dataOutputStream;

    public Connection(String host, int port) throws IOException {
        this.socket = new Socket(host, port);
        this.dataInputStream = new DataInputStream(socket.getInputStream());
        this.dataOutputStream = new DataOutputStream(socket.getOutputStream());

        this.callbackPool = Executors.newFixedThreadPool(4);

        Thread t = new Thread(() -> {
            try {
                while (!socket.isClosed()) {
                    // 读取响应
                    Response response = readResponse();
                    // 处理响应
                    dispatchResponse(response);
                }
            } catch (SocketException e) {
                log.info("连接已断开");
            } catch (ClassNotFoundException | MqException | IOException e) {
                log.warn("连接异常断开");
                e.printStackTrace();
            }
        });
        t.start();
    }

    // 关闭连接
    public void close() {
        callbackPool.shutdownNow();
        channelMap.clear();
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 读取响应
    private Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());

        byte[] payload = new byte[response.getLength()];
        int actual = dataInputStream.read(payload);
        if (actual != response.getLength()) throw new IOException("响应数据不完整");
        response.setPayload(payload);

        log.info("接收响应, type=" + response.getType() + ", length=" + response.getLength());
        return response;
    }

    // 处理响应
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        if (response.getType() == 0xc) { // 此时该响应为服务器推送的消息
            SubscribeReturns subscribeReturns = (SubscribeReturns) BinaryTool.fromBytes(response.getPayload());
            Channel channel = channelMap.get(subscribeReturns.getChannelId());
            if (channel == null) {
                throw new MqException("客户端不存在的会话, channelId=" + subscribeReturns.getChannelId());
            }
            // 在线程池中处理客户自定义的回调函数
            callbackPool.submit(() -> {
                try {
                    channel.getConsumer().handleDelivery(subscribeReturns.getConsumerTag(),
                            subscribeReturns.getBasicProperties(), subscribeReturns.getBody());
                } catch (MqException | IOException e) {
                    throw new RuntimeException(e);
                }
            });
        } else { // 0x1-0xb 均为服务器对客户端控制方法的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            // 将 basicReturns 与 channel 对应，放入相应 basicReturnsMap
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if (channel == null) {
                throw new MqException("客户端不存在的会话, channelId=" + basicReturns.getChannelId());
            }
            channel.notifyChannel(basicReturns);
        }
    }

    // 发送请求（该方法由 Channel 调用，请求的构造在 Channel 中完成）
    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        log.info("请求已发送, type=" + request.getType() + ", length=" + request.getLength());
    }

    // 创建会话。作为上层方法，封装对 Channel 的操作。
    public Channel createChannel() throws IOException {
        // 在客户端创建出 Channel
        String channelId = "C-" + UUID.randomUUID();
        Channel channel = new Channel(channelId, this);

        /*
         * 必须先将新创建的 channel 保存在客户端，再通过该 channel 请求服务器。
         * 原因：Connection 的 t 线程会持续尝试接收服务器的响应。若先请求服务器，则当服务器的响应进入 dispatchResponse
         *      处理时，channelMap 中尚未保存该新建的 channel。程序将抛出 ‘客户端不存在的会话’ 异常，无法将设置结果告知
         *      channel。channel 从而一直阻塞在 waitResult。
         */

        channelMap.put(channelId, channel);

        // 请求服务器，在服务器端记录该 Channel 对象
        boolean ok = channel.createChannel();
        if (!ok) channelMap.remove(channelId);

        return channel;
    }
}
